import serial
import os
"""
    2021-08-31  15:10   by  CYL
    起因：
        ESP8266的Aruino开发环境由于网络问题无法下载。
    文件名：
        ESP8266pyByCYL2.py
    文件说明：
        除findSerialPort()、serial_Port_Init()外，均已ESP8266特化。
        在demo2plus的基础上，移除MCUUARTpyByCYL.py，将findSerialPort()、serial_Port_Init()移入本文件中。
    修改历史：
        改进sendStringToESP8266()，去除内部的readBytesFromESP8266()，防止出现奇怪的返回问题。
        改进readBytesFromESP8266()，使其能返回真实的来自ESP8266的str列表。
        新增传输.py文件给ESP8266(),并从ESP8266中返回执行结果。
    使用前提：
        安装过esptool、pyserial。
        以及烧写了micropython固件的ESP8266。
    注意：
        使用本文件时，ESP8266的板子要和电脑连接才可以。
    其它：
    ESP8266-MicroPython固件网址：
        http://micropython.org/download/esp8266/
    ESP8266-MicroPython烧写教程：
        http://docs.micropython.org/en/latest/esp8266/tutorial/intro.html
    ESP8266-MicroPython烧写大致流程：
        先下载固件，然后在CMD中，pip install esptool，然后esptool.py --port COMx（通过CMD的mode查看串口号） erase_flash来擦除ESP8266的Flash。
        接着将固件烧写进ESP8266，esptool.py --port COMx --baud 460800 write_flash --flash_size=detect 0 esp8266-xxx-vx.x.x.bin
        NodeMCU:    esptool.py --port COMx --baud 460800 write_flash --flash_size=detect -fm dout 0 esp8266-xxx-vx.x.x.bin
        Linux只需要把上述COMx改为/dev/ttyUSB0即可。
        重新拔插ESP8266板子即可。
        更详细的可以参考上述的烧写教程。
"""
def findSerialPort()->str:
    """
        2021-08-31  13:31   by  炸天帮成员
        函数名：
            findSerialPort()
        函数用途：
            用于查询电脑端可用的串口。
        类型：
            通用型函数。
    :return:    返回一个字符串，内容为有挂载的串口号。
    """
    serial_ports = os.popen('python -m serial.tools.list_ports').read().strip()  # 调用控制台并读取cmd命令行的结果
    print('serial_ports = ', serial_ports,type(serial_ports),"aim:用于查看当前有效COM端口号")  # 显示当前有效的COM端
    return serial_ports

def serial_Port_Init(serial_ports:str)->serial.serialwin32.Serial:
    """
        2021-08-31  13:33   by  炸天帮成员
        函数名：
            serial_Port_Init()
        函数用途：
            用于设置UART的通讯参数。
        类型：
            通用型函数。
    :param serial_ports:    为一个有挂载的串口端口号，一般来自findSerialPort()。
    :return:                serial.serialwin32.Serial类型的对象。
    """
    ser = serial.Serial()       # 实例化serial_com端口
    ser.port = serial_ports     # 设置端口号
    ser.baudrate = 115200       # 设置端口的波特率115200
    ser.bytesize = serial.EIGHTBITS
    ser.parity = serial.PARITY_NONE
    ser.stopbits = serial.STOPBITS_ONE
    ser.timeout = 0.01          # 设定读取超时时间0.1s
    ser.xonxoff = False
    ser.rtscts = False
    ser.write_timeout = None    # Set a write timeout value.
    ser.dsrdtr = False
    ser.inter_byte_timeout = None
    print("ser = ",ser,type(ser),"aim:用于查看当前端口的详细信息。")  # 输出当前端口的详细信息
    return ser

def readBytesFromESP8266(targetSerial:serial.serialwin32.Serial,repeatTime:int=10)->bytes:
    """
        2021-08-31  13:35   by  炸天帮成员
        函数名：
            readBytesFromESP8266()
        函数用途：
            用于获取来自ESP8266的UART返回值。在通用型的基础上做了去除空byte流的操作。
            通过修改repeatTime，可以对ESP8266的返回内容做出条数删选。
            大幅修改返回值。
        函数类型：
            MicroPython特化函数。
            将if和for去除后，即为单次通用型函数readBytesFromUART()。
        关联函数：
            sendStringToESP8266()
    :param targetSerial:    一般为ESP8266所在的端口号所对应的serial.serialwin32.Serial类型的对象。一般来自serial_Port_Init()。
    :param repeatTime:  xxx.read()重复扫描的次数，间隔时间在serial_Port_Init()中的timeout中设置。可以在一定程度上解决EOF遇到0x0d0x0a后的问题。
    :return:    resultList为未去除\r\n的原始返回值，为一个列表，内容物为字符串。
                resultStr.split("\r\n") 为去除\r\n的返回值，为一个列表，内容物为字符串，一般倒数第二个(即[-2])为真实返回值。
    """
    resultList = [0]
    for looptime in range(0,repeatTime):
        data_return = targetSerial.read()  # 可以按字节数读取来自下位机的串口内容，此处设定为100字节
        if data_return != b'':
            #print('data_return = ', data_return)       # 显示从COM端读取到的内容，一般为第一个字符。
            data_in_waiting = targetSerial.read(targetSerial.in_waiting)
            #print('ser.in_waiting =', data_in_waiting) # 显示剩余的COM端的内容。
            result = data_return + data_in_waiting      # 拼接两个Bytes数据。
            print('esp8266返回.$>_', result.decode(encoding="UTF-8"),'aim:用于查看ESP8266的UART返回值。')
            resultList.append(bytes.decode(result))     # bytes转换为str。
        else:
            pass
    resultList.pop(0)
    # print("resultList = ",resultList,len(resultList),"aim:显示PC端UART获取的结果，未处理\r\n。")
    resultStr = ""
    if len(resultList) > 0: #   如果列表内容不为空，则将字符串列表成员拼接为单个字符串。
        for item in range(0,len(resultList)):
            resultStr = resultStr + resultList[item]
        # return resultList                     #   返回未去除\r\n的ESP8266返回值，为一个字符串列表。
        return resultStr.split("\r\n")          #   返回去除\r\n的ESP8266返回值，为一个字符串列表。
        # return resultStr.split("\r\n")[-2]    #   返回值一般都是倒数第二个列表元素，为一个字符串。
    else:
        pass

def sendStringToESP8266(targetSerial:serial.serialwin32.Serial,theContent: str)->bytes:
    """
        2021-08-31  16:20   BY  炸天帮成员
        函数名：
            sendStringToESP8266()
        函数用途：
            用于向ESP8266中传入Bytes字节流。去除了原本的读取功能，故想要获取UART返回值的话，就需要调用readBytesFromESP8266()
        函数类型：
            MicroPython特化函数。
            去除readBytesFromESP8266()后，为普通无读取功能的通用型函数。
        关联函数：
            readBytesFromESP8266()
        后续：
            可以将传入字符串改为传入一个文本文件，然后就可以在.txt中编程。
            已完成，见send_pyFileToESP8266()。
    :param targetSerial:一般为ESP8266所在的端口号所对应的serial.serialwin32.Serial类型的对象。一般来自serial_Port_Init()。
    :param theContent:  用于向ESP8266中传入Bytes字节流所对应的字符串内容。
    :return:
    """
    send_bytes = bytes(theContent, encoding='UTF-8') + b'\x0d\x0a'
    send_string = send_bytes.decode('UTF-8')
    #print('send_bytes = ', send_bytes, 'send_bytes 的类型为：', type(send_bytes), '\nsend_string = ', send_string,' send_string 的类型为', type(send_string),"aim:显示发送给ESP8266的内容。")
    targetSerial.write(send_bytes)                  # 发送数据至ESP8266。


# ------
def read_pyFileForMicroPython(pyfilePath:str)->list:
    """
        2021-08-31  23:19   by  CYL
        函数名：
            read_pyFileForMicroPython()
        函数说明：
            用于将.py文件中的内容格式化成sendStringToESP8266()能够操作的字符串。
        使用方法：
            为send_pyFileToESP8266()使用前的必要步骤，为该函数的第二参数。
        microPython特化的.py文件的书写要求：
            1)在使用microPython时，while、for、if语句(无论是否嵌套)后边结束时都要跟随3次回车。
            2)参见test.py样例。
            3)使用def时，结束时也要3次回车。即有缩进语句存在的地方都要这样操作。（3次回车是目前MicroPython中行缩进的结束符）
            4)其余暂时和普通python一致。
    :param pyfilePath:  .py文件的所在路径。
    :return:    去除\n后的格式化后的.py文件每行的内容。用于send_pyFileToESP8266()的第二参数。
    """
    f = open(pyfilePath, "r",encoding="utf-8")
    f_content = f.read()
    f.close()
    print("f_content = ",f_content,"aim:用于查看.py中的内容")
    return f_content.split("\n")

def send_pyFileToESP8266(targetSerial:serial.serialwin32.Serial,pyFileContent:list):
    """
        2021-08-31  23:26   by  CYL
        函数名：
            send_pyFileToESP8266()
        函数说明：
            将一个可运行的.py文件传入ESP8266的python编译器中。
        相似函数：
            sendStringToESP8266()
    :param targetSerial: 一般为ESP8266所在的端口号所对应的serial.serialwin32.Serial类型的对象。一般来自serial_Port_Init()。
    :param pyFileContent: 被格式化后的.py文件中每行的语句的字符串内容。一般来自read_pyFileForMicroPython()。
    :return:    无。
    """
    for item in range(0, len(pyFileContent)):
        sendStringToESP8266(targetSerial=targetSerial, theContent=pyFileContent[item])

def ESP8266pyUARTConnect_demo(targetSerial:serial.serialwin32.Serial):
    """
        函数名：
            ESP8266pyUARTConnect_demo()
        函数说明：
            使用ESP8266特化型的函数，从而来实现和MCUpyUARTConnect()相类似的功能。
            在demo3中就是个标准编写模板。
    :param targetSerial:    serial.serialwin32.Serial对象，一般由serial_Port_Init()获得。
    :return:
    """
    targetSerial.open()  # 开启ESP8266端口
    if targetSerial.is_open:

        sendStringToESP8266(targetSerial=targetSerial,theContent="import machine")
        sousa0 = readBytesFromESP8266(targetSerial=targetSerial)
        print("sousa0 = ", sousa0, "aim:用于查看操作0的返回值",'\n')

        sendStringToESP8266(targetSerial=targetSerial,theContent="machine.freq()")
        sousa1 = readBytesFromESP8266(targetSerial=targetSerial)
        print("sousa1 = ",sousa1,"aim:用于查看操作1的返回值",'\n')

        sendStringToESP8266(targetSerial=targetSerial, theContent="1 + 1")
        sousa2 = readBytesFromESP8266(targetSerial=targetSerial)
        print("sousa2 = ",sousa2,"aim:用于查看操作2的返回值",'\n')

        #------
        # sousa3全系列用于检测microPython在使用含有缩进内容的python语句时，该如何操作。
        # 现发现,microPython就是在RSP8266中加载了一个cmd环境的python。有缩进语句时，会出现...的返回，和cmd是一样的。

        # sendStringToESP8266(targetSerial=targetSerial, theContent="for i in range(0,2):")
        # sendStringToESP8266(targetSerial=targetSerial, theContent="i=i+1")
        # sendStringToESP8266(targetSerial=targetSerial, theContent="\n")   #   用于检测micropython的结束符
        # sendStringToESP8266(targetSerial=targetSerial, theContent="\n")
        # sendStringToESP8266(targetSerial=targetSerial, theContent="\n")
        # sendStringToESP8266(targetSerial=targetSerial, theContent="print(i)")
        # sousa3 = readBytesFromESP8266(targetSerial=targetSerial)
        #
        # sendStringToESP8266(targetSerial=targetSerial, theContent="if i==2:")
        # sendStringToESP8266(targetSerial=targetSerial, theContent="i=i+1")
        # sendStringToESP8266(targetSerial=targetSerial, theContent="\n")   #   用于检测micropython的结束符
        # sendStringToESP8266(targetSerial=targetSerial, theContent="\n")
        # sendStringToESP8266(targetSerial=targetSerial, theContent="\n")
        # sendStringToESP8266(targetSerial=targetSerial, theContent="print(i)")
        # sousa3 = readBytesFromESP8266(targetSerial=targetSerial)
        # print("sousa3 = ",sousa3,"aim:用于查看操作3的返回值",'\n')

        # test_pyFile_content = read_pyFileInMicroPython("./test.py")
        # for item in range(0,len(test_pyFile_content)):                    #   首次构造传输.py文件给ESP8266。
        #     sendStringToESP8266(targetSerial=targetSerial, theContent=test_pyFile_content[item])
        # sousa3 = readBytesFromESP8266(targetSerial=targetSerial)
        # print("sousa3 = ",sousa3,"aim:用于查看操作3的返回值",'\n')

        send_pyFileToESP8266(targetSerial=targetSerial,pyFileContent=read_pyFileForMicroPython("./test.py"))    #   最终的传输.py文件给ESP8266的操作示范。
        sousa3 = readBytesFromESP8266(targetSerial=targetSerial)
        print("sousa3 = ",sousa3,"aim:用于查看操作3的返回值",'\n')

    targetSerial.close()  # 关闭ESP8266端口